package edu.pku.sei.transformation.pkuengine;


import java.util.ArrayList;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import edu.pku.sei.modeler.model.core.MetaClassifier;
import edu.pku.sei.modeler.model.core.MetaStructuralFeature;
import edu.pku.sei.transformation.editor.model.AtomicTransformation;
import edu.pku.sei.transformation.editor.model.Comment;
import edu.pku.sei.transformation.editor.model.CommentConnection;
import edu.pku.sei.transformation.editor.model.Expression;
import edu.pku.sei.transformation.editor.model.GraphicalDomain;
import edu.pku.sei.transformation.editor.model.Key;
import edu.pku.sei.transformation.editor.model.Rule;
import edu.pku.sei.transformation.editor.model.RuleInvoke;
import edu.pku.sei.transformation.editor.model.RuleInvokeParameter;
import edu.pku.sei.transformation.editor.model.execution.Model;
import edu.pku.sei.transformation.editor.model.pattern.Pattern;
import edu.pku.sei.transformation.editor.model.variable.ConnectionVariable;
import edu.pku.sei.transformation.editor.model.variable.ObjectVariable;
import edu.pku.sei.transformation.editor.model.variable.Variable;
import edu.pku.sei.transformation.pkuengine.equivalencegraph.EquivalenceGraph;
import edu.pku.sei.transformation.util.parser.expression.Parser;
import edu.pku.sei.transformation.util.parser.expression.element.AttributeExpression;
import edu.pku.sei.transformation.util.parser.expression.element.CharacterConstant;
import edu.pku.sei.transformation.util.parser.expression.element.FloatingConstant;
import edu.pku.sei.transformation.util.parser.expression.element.Identifier;
import edu.pku.sei.transformation.util.parser.expression.element.IntegerConstant;
import edu.pku.sei.transformation.util.parser.expression.element.OperationExpression;
import edu.pku.sei.transformation.util.parser.expression.element.StringConstant;
import edu.pku.sei.transformation.util.parser.texttemplate.element.ElementEdge;
import edu.pku.sei.transformation.util.parser.texttemplate.element.PlainTextElement;
import edu.pku.sei.transformation.util.parser.texttemplate.element.StructuredTextElement;
import edu.pku.sei.transformation.util.parser.texttemplate.element.TextElement;
import edu.pku.sei.transformation.util.parser.texttemplate.element.TextElementRegister;
import edu.pku.sei.transformation.util.parser.texttemplate.element.VariableTextElement;

public class XMLExporter {
	public static void transformationToXML(AtomicTransformation trans, Element root,
			Document doc) {
		calculateTemplateWeight(trans);
		
		root.setAttribute("name", trans.getName());
		Element models = doc.createElement("typedKeys");
		root.appendChild(models);
		for (Object obj : trans.getModelParameter()) {
			Element model = doc.createElement("typekey");
			models.appendChild(model);
			typedModelToXML((Model) obj, model, doc);
			for (Object obj2 : trans.getKey()) {
				if (((Key) obj2).getKeyClass().getModel() == ((Model) obj)
						.getMetamodel().getType()) {
					Element k = doc.createElement("keys");
					model.appendChild(k);
					keyToXML((Key) obj2, k, doc);
				}
			}
		}
		Element modelTypes = doc.createElement("modeltypes");
		root.appendChild(modelTypes);
		for (Object obj : trans.getModelParameter()) {
			Element model = doc.createElement("modeltype");
			modelTypes.appendChild(model);
			model.setAttribute("name", ((Model) obj).getName());
			model.setAttribute("type", ((Model) obj).getMetamodel()
					.getName());
		}

		Element rules = doc.createElement("rules");
		root.appendChild(rules);

		for (Object obj : trans.getRules()) {
			Element relation = doc.createElement("relation");
			rules.appendChild(relation);
			ruleToXML((Rule) obj, relation, doc);
		}
	}

	private static void keyToXML(Key key, Element root, Document doc) {
		root.setAttribute("type", key.getKeyClass().getName());
		for (Object obj : key.getKeyAttributes()) {
			Element attribute = doc.createElement("key");
			root.appendChild(attribute);
			attribute.setTextContent(((MetaStructuralFeature) obj).getName());
		}
	}

	private static void typedModelToXML(Model model, Element root,
			Document doc) {
		root.setAttribute("name", model.getMetamodel().getName());
		//root.setAttribute("file", model.getMetamodel().getAddress());
	}

	private static void ruleToXML(Rule rule, Element root, Document doc) {
		root.setAttribute("name", rule.getName());
		root.setAttribute("priority", "0");

		Element variables = doc.createElement("variables");
		root.appendChild(variables);
		for (Object obj : rule.getVariables()) {
			Element variable = doc.createElement("variable");
			variables.appendChild(variable);
			variableToXML((Variable) obj, variable, doc);
		}

		for (Object obj : rule.getDomains()) {
			Element domain = doc.createElement("domain");
			root.appendChild(domain);
			domainToXML((GraphicalDomain) obj, domain, doc);
		}

		Element when = doc.createElement("when");
		root.appendChild(when);
		Element where = doc.createElement("where");
		root.appendChild(where);
		for (Object obj : rule.getInvoke()) {
			Element invoke = doc.createElement("expression");
			RuleInvoke r = (RuleInvoke) obj;
			if (r.getInvokeType() == r.RULE_POSTCONDITION) {
				where.appendChild(invoke);
			} else {
				when.appendChild(invoke);
			}
			invokeToXML((RuleInvoke) obj, invoke, doc);
		}
	}

	private static void variableToXML(Variable v, Element root, Document doc) {
		root.setAttribute("name", v.getName());
		root
				.setAttribute("type", ((MetaClassifier) v.getType())
						.getName());
	}

	private static void domainToXML(GraphicalDomain d, Element root,
			Document doc) {
		if (d.getTypedModel() != null)
			root.setAttribute("modeltype", d.getTypedModel().getName());
		else
			root.setAttribute("modeltype", "null");
		
		Element pattern = doc.createElement("pattern");
		root.appendChild(pattern);
		patternToXML(d.getPattern(), pattern, doc);
	}

	private static void patternToXML(Pattern p, Element root, Document doc) {
		Element exps = doc.createElement("expressions");
		root.appendChild(exps);
		Element nodes = doc.createElement("nodes");
		root.appendChild(nodes);
		Element edges = doc.createElement("edges");
		root.appendChild(edges);
		for (Object obj : p.getNodes()) {
			if (obj instanceof ObjectVariable) {
				Element node = doc.createElement("node");
				nodes.appendChild(node);
				objectVariableToXML((ObjectVariable) obj, node, doc);
				for (Object obj2 : ((ObjectVariable) obj).getExpressions()) {
					Expression exp = (Expression) obj2;
					edu.pku.sei.transformation.util.parser.expression.element.Expression o = Parser
					.parse(exp.getExp());
					if (o != null) {
						Element expEle = doc.createElement("expression");
						exps.appendChild(expEle);
						expressionToXML(o, expEle, doc);
					}
				}
			} else if (obj instanceof Comment) {
				Comment c = (Comment) obj;
				for (TextElement element : c.getAllNodes()) {
					Element node = doc.createElement("node");
					nodes.appendChild(node);
					templateElementToXML(element, node, doc);
				}
				for (ElementEdge link : c.getAllLinks()) {	
					Element edge = doc.createElement("edge");
					edges.appendChild(edge);
					edge.setAttribute("source", link.getSourceTextElement().getName());
					edge.setAttribute("target", link.getTargetTextElement().getName());
					edge.setAttribute("tarOrder", String.valueOf(link.getWeight()));
				}
			}
		}
		for (Object obj : p.getLinks()) {
			if (obj instanceof ConnectionVariable) {
				Element edge = doc.createElement("edge");
				edges.appendChild(edge);
				connectionVariableToXML((ConnectionVariable) obj, edge, doc);				
			} else if (obj instanceof CommentConnection) {
				CommentConnection cc = (CommentConnection) obj;
				if (cc.getSource() instanceof ObjectVariable) {
					ObjectVariable source = (ObjectVariable) cc.getSource();
					Comment c = (Comment) cc.getTarget();
					if (c.getRegister() != null) {
						for (ElementEdge link : c.getRegister().getRoot().getEdges()) {
							Element edge = doc.createElement("edge");
							edges.appendChild(edge);
							edge.setAttribute("source", source.getName());
							edge.setAttribute("target", link.getTargetTextElement().getName());
							edge.setAttribute("tarOrder", String.valueOf(link.getWeight()));
						}						
					}
				}
			}
		}

	}


	private static void templateElementToXML(TextElement element,
			Element root, Document doc) {
		root.setAttribute("name", element.getName());
		if (element instanceof StructuredTextElement) {
			root.setAttribute("type", "StructuredTextElement");
		} else if (element instanceof VariableTextElement) {
			root.setAttribute("type", "VariableTextElement");
		} else if (element instanceof PlainTextElement){
			root.setAttribute("type", "PlainTextElement");
		}
		
	}

	private static void objectVariableToXML(ObjectVariable o, Element root,
			Document doc) {
		root.setAttribute("name", o.getName());
		if (o.getModel() != null)
			root.setAttribute("type", ((MetaClassifier) o.getType())
					.getName());
		else 
			root.setAttribute("type","null");
	}

	private static void connectionVariableToXML(ConnectionVariable c,
			Element root, Document doc) {
		root.setAttribute("source", c.getSource().getName());
		root.setAttribute("target", c.getTarget().getName());
		root.setAttribute("srcName", c.getSourceRole());
		root.setAttribute("tarName", c.getTargetRole());
	}

	private static void invokeToXML(RuleInvoke r, Element root, Document doc) {
		root.setAttribute("name", "CallRelationExpression");

		Element relation = doc.createElement("relation");
		root.appendChild(relation);
		relation.setTextContent(r.getCallee().getName());

		for (Object obj : r.getParameters()) {
			RuleInvokeParameter p = (RuleInvokeParameter) obj;
			Element parameter = doc.createElement("parameter");
			root.appendChild(parameter);

			Element from = doc.createElement("from");
			parameter.appendChild(from);
			Element fromexp = doc.createElement("expression");
			from.appendChild(fromexp);
			fromexp.setAttribute("name", "BindExpression");
			Element fromname = doc.createElement("name");
			fromexp.appendChild(fromname);
			if (p.getFormalParameter() != null) {
				fromname.setTextContent(((RuleInvokeParameter) obj).getFormalParameter()
						.getName());				
			} else {
				fromname.setTextContent("null");
			}

			Element to = doc.createElement("to");
			parameter.appendChild(to);
			Element toexp = doc.createElement("expression");
			to.appendChild(toexp);
			expressionToXML(p.getActualParameter().getSyntaxTree(), toexp, doc);
			/*toexp.setAttribute("name", "BindExpression");
			Element toname = doc.createElement("name");
			toexp.appendChild(toname);
			if (((Parameter) obj).getActualParameter() != null) {
				toname.setTextContent(((Parameter) obj).getActualParameter()
						.getIdentifier());				
			} else {
				toname.setTextContent("null");
			}*/
		}
	}

	private static void expressionToXML(
			edu.pku.sei.transformation.util.parser.expression.element.Expression exp,
			Element root, Document doc) {
		if (exp instanceof Identifier || exp instanceof AttributeExpression) {
			root.setAttribute("name", "BindExpression");
			Element name = doc.createElement("name");
			root.appendChild(name);
			name.setTextContent(exp.toString());
		} else if (exp instanceof OperationExpression) {
			OperationExpression opExp = (OperationExpression) exp;
			if ("=".equals(opExp.getOperator())) {
				root.setAttribute("name", "EqualExpression");

				Element left = doc.createElement("left");
				root.appendChild(left);
				Element leftExp = doc.createElement("expression");
				left.appendChild(leftExp);
				expressionToXML(opExp.getLeftExpression(), leftExp, doc);

				Element right = doc.createElement("right");
				root.appendChild(right);
				Element rightExp = doc.createElement("expression");
				right.appendChild(rightExp);
				expressionToXML(opExp.getRightExpression(), rightExp, doc);
			} else if ("+".equals(opExp.getOperator())) {

			}
		} else if (exp instanceof CharacterConstant
				|| exp instanceof FloatingConstant
				|| exp instanceof IntegerConstant
				|| exp instanceof StringConstant) {
			root.setAttribute("name", "ConstantExpression");
			root.setTextContent(exp.toString());
		}
	}
	
	private static void calculateTemplateWeight(AtomicTransformation trans) {
		EquivalenceGraph graph = new EquivalenceGraph();
		ArrayList<TextElementRegister> result = new ArrayList<TextElementRegister>();
		for (Object obj : trans.getRules()) {
			Rule rule = (Rule) obj;
			for (Object obj2 : rule.getDomains()) {
				if (obj2 instanceof GraphicalDomain) {
					for (Comment comment : ((GraphicalDomain) obj2).getComment()) {
						if (comment.getRegister() != null) {
							graph.createNode(comment.getRegister().getRoot());
							result.add(comment.getRegister());
						}
					}
				}
			}
		}
		
		for (Object obj : trans.getRules()) {
			Rule rule = (Rule) obj;
			for (Object obj2 : rule.getInvoke()) {
				RuleInvoke con = (RuleInvoke) obj2;
				if (con.getInvokeType() == con.RULE_POSTCONDITION) {
					for (Object obj3 : con.getParameters()) {
						RuleInvokeParameter p = (RuleInvokeParameter) obj3;
						p.getActualParameter().bind();
						if (p.getActualParameter().getSyntaxTree() instanceof Identifier && ((Identifier)p.getActualParameter().getSyntaxTree()).getVariable() instanceof StructuredTextElement
								&& p.getFormalParameter() instanceof StructuredTextElement) {
							TextElement element1 = (StructuredTextElement)p.getFormalParameter();
							TextElement element2 = ((StructuredTextElement)((Identifier)p.getActualParameter().getSyntaxTree()).getVariable());
							graph.getEquivalenceNode(element1).merge(graph.getEquivalenceNode(element2));
						}
							
					}
				}
			}
		}
		
		graph.linkTopologicalSort();
		
		for (TextElementRegister templateElementRegister : result) {
			System.out.println(templateElementRegister.toString());
			System.out.println();
		}
	}
}
